import android.content.Context;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.util.Log;

import java.lang.ref.WeakReference;
import java.util.Arrays;

public class AudioCapturer {

    private static final String TAG = AudioCapturer.class.getSimpleName();
    private static final int ABITRATE_KBPS = 24;
    private static final String ACODEC = "audio/mp4a-latm";

    // audio device.
    private AudioRecord mMic;
    private byte[] mFrameBuffer;
    private byte[] mRecordBuffer;
    private byte[] mLeftBuffer;

    // use worker thread to get audio packet.
    private Thread mWorker;
    private boolean mLoop;

    // audio mic settings.
    private int mSampleRate = DDAudioDef.SAMPLE_RATE_44100;
    private int mChannel = DDAudioDef.CHANNELS_MONO;
    private int mBits = DDAudioDef.BITS_PER_PCM_UNIT_16;

    // 静音
    private boolean mMute = false;
    private boolean mbPause = false;
    private AudioListener mListener;
    private boolean mMicPermit = false;
    private boolean mEnableAEC = false;

    public boolean start(int sampleRate, int channel, int bit, boolean enableAEC, AudioListener listener) {
        stop();
        mSampleRate = sampleRate;
        mChannel = channel;
        mBits = bit;
        mListener = listener;
        mEnableAEC = enableAEC;

        // open mic, to find the work one.
        if ((mMic = chooseAudioDevice()) == null) {
            Log.e(TAG, String.format("mic find device mode failed."));
            mMicPermit = false;
            return false;
        }

        mMicPermit = true;

        try {
            mMic.startRecording();
            // Log.i(TAG, String.format("start the mic in rate=%dHZ, channels=%d, format=%d", mSampleRate, mChannel, mBits));
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "mic startRecording failed.");
            mMicPermit = false;
            return false;
        }

        // start audio worker thread.
        mWorker = new Thread(new Runnable() {
            @Override
            public void run() {
                fetchAudioFromDevice();
            }
        });
        mWorker.setName("AudioSysRecord");
        Log.i(TAG, "start audio worker thread.");
        mLoop = true;
        mWorker.start();
        return true;
    }

    public void stop() {
        mSampleRate = DDAudioDef.SAMPLE_RATE_44100;
        mChannel = DDAudioDef.CHANNELS_MONO;
        mBits = DDAudioDef.BITS_PER_PCM_UNIT_16;
        mListener = null;
        mEnableAEC = false;

        mLoop = false;
        if (mWorker != null) {
            //mWorker.interrupt();
            try {
                mWorker.join();
            } catch (Exception e) {
                e.printStackTrace();
            }
            mWorker = null;
        }

        if (mMic != null) {
            try {
                mMic.setRecordPositionUpdateListener(null);
                mMic.stop();
                mMic.release();
                mMic = null;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void pause() {
        mbPause = true;
    }

    public void resume() {
        mbPause = false;
    }

    public void setMute(boolean mute) {
        mMute = mute;
    }

    private AudioRecord chooseAudioDevice() {
        int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
        if (mChannel == 1) {
            channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
        }

        int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
        if (mBits == 8) {
            audioFormat = AudioFormat.ENCODING_PCM_8BIT;
        }
        //int bufferSize = 2 * bSamples * nChannels / 8;
        int bufferSize = AudioRecord.getMinBufferSize(mSampleRate, channelConfig, audioFormat);
        try {
            if (!mEnableAEC)
                mMic = new AudioRecord(MediaRecorder.AudioSource.MIC, mSampleRate, channelConfig, audioFormat, bufferSize * 2);
            else{
                mMic = new AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION, mSampleRate, channelConfig, audioFormat, bufferSize * 2);
            }
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        }

        if (mMic == null || mMic.getState() != AudioRecord.STATE_INITIALIZED) {
            Log.e(TAG, "initialize the mic failed.");
            mMicPermit = false;
            return null;
        }

        mRecordBuffer = new byte[bufferSize];
        mFrameBuffer = new byte[1024 * mBits * mChannel / 8];
        mLeftBuffer = new byte[mFrameBuffer.length];
        Log.i(TAG, String.format("mic open rate=%dHZ, channels=%d, bits=%d, buffer=%d/%d, state=%d",
                mSampleRate, mChannel, mBits, bufferSize, mFrameBuffer.length, mMic.getState()));
        return mMic;
    }

    private void fetchAudioFromDevice() {
        int readSize = 0;
        int leftPcmDataSize = 0;

        int nFailedCount = 0;
        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
        while (mLoop && mMic != null && !Thread.interrupted() && nFailedCount <= 5) {

            if (mRecordBuffer.length >= mFrameBuffer.length) { //缓冲区大小大于一帧，一次读取一帧
                readSize = mMic.read(mFrameBuffer, 0, mFrameBuffer.length);

                if (readSize != mFrameBuffer.length) {
                    if (readSize <= 0) {
                        Log.e(TAG, "read pcm eror, len =" + readSize);
                        nFailedCount++;
                    }
                    continue;
                }

                nFailedCount = 0;
                if(mStartTick == 0)mStartTick = DDUtils.getTick();
                onGetPcmFrame(mFrameBuffer, (int)(DDUtils.getTick() - mStartTick));
            } else { //缓冲区不够一帧，就要分多次取
                readSize = mMic.read(mRecordBuffer, 0, mRecordBuffer.length);
                if (readSize != mRecordBuffer.length) {
                    if (readSize <= 0) {
                        Log.e(TAG, "read pcm eror, len =" + readSize);
                        nFailedCount++;
                    }
                    continue;
                }

                nFailedCount = 0;
                //如果上一次剩余的数据加这一次读取的数据大于一帧了就编码发送
                if (readSize + leftPcmDataSize >= mFrameBuffer.length) {

                    //先把上次剩余的数据拷贝到发送buffer里
                    if (leftPcmDataSize > 0) {
                        System.arraycopy(mLeftBuffer, 0, mFrameBuffer, 0, leftPcmDataSize);
                    }

                    //把本次读取的帧也拷贝到发送buffer里
                    int needNum = mFrameBuffer.length - leftPcmDataSize; //还需要copy多少数据才够一帧
                    System.arraycopy(mRecordBuffer, 0, mFrameBuffer, leftPcmDataSize, needNum);

                    //还有剩余的数据
                    if (readSize > needNum) {
                        System.arraycopy(mRecordBuffer, needNum, mLeftBuffer, 0, readSize - needNum);
                        leftPcmDataSize = readSize - needNum;
                    } else {
                        leftPcmDataSize = 0;
                    }
                    if(mStartTick == 0)mStartTick = DDUtils.getTick();
                    onGetPcmFrame(mFrameBuffer, (int)(DDUtils.getTick() - mStartTick));
                } else { //数据不够，更新一下剩余数据信息
                    System.arraycopy(mRecordBuffer, 0, mLeftBuffer, leftPcmDataSize, readSize);
                    leftPcmDataSize += readSize;
                }
            } //end if
        } //end while

        mListener = null;

        if (nFailedCount > 5) {
            if(mListener != null){
                mListener.onErr(-1);
            }
            mMicPermit = false;
        }
    }

    private void onGetPcmFrame(byte[] data, long timestamp) {
        if (mMute) {
            Arrays.fill(data, (byte) 0);
        }
        if (!mbPause) {
            onCapturePcmData(data, timestamp);
        }
    }

    /*
    * 检测麦克风权限
     */
    public boolean checkPermission() {
        return mMicPermit;
    }

    private void onCapturePcmData(byte[] data, long timestamp) {
        if (null != mListener) {
            mListener.onPCM(data, timestamp, mChannel, mSampleRate);
        }
    }

    private long mStartTick;
    private long mNativeProcessor = 0L;
}
